Skip to content

Honor qualified Cache-Control: private="field" in a shared cache (RFC9111 section 5.2.2.7) - #868

Open
arturobernalg wants to merge 1 commit into
apache:masterfrom
arturobernalg:cache-qualified-private
Open

Honor qualified Cache-Control: private="field" in a shared cache (RFC9111 section 5.2.2.7)#868
arturobernalg wants to merge 1 commit into
apache:masterfrom
arturobernalg:cache-qualified-private

Conversation

@arturobernalg

Copy link
Copy Markdown
Member

A shared cache now stores a response that carries a qualified private directive with the named header fields removed from the stored copy, instead of treating the whole response as non-cacheable, while the response returned to the caller retains those fields. The fields are removed from freshly stored entries, from entries updated by a 304 revalidation, and from the root entry of a Vary response. A bare private directive still makes the whole response non-storable by a shared cache, and multiple qualified private directives accumulate their field names.

@arturobernalg
arturobernalg force-pushed the cache-qualified-private branch from e07363a to a501878 Compare August 16, 2026 17:32
@arturobernalg
arturobernalg requested a review from ok2c August 16, 2026 17:39
}

static HttpCacheEntry stripPrivateFields(final HttpCacheEntry entry) {
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturobernalg This looks conceptually wrong. Why do we need to re-parse the headers and re-create ResponseCacheControl here? In fact this is the wrong architectural layer for such a decision. This is the responsibility of the protocol layer, not of a caching backend. Pull this logic up into CachingExec / AsyncCachingExec

} else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_PRIVATE)) {
builder.setCachePrivate(true);
// A repeated private directive replaces any earlier one; only the last takes effect.
privateFields.clear();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturobernalg This whole thing can be condensed into just a few lines of code. Why the parsing logic should be any different that that of CACHE_CONTROL_NO_CACHE?

class TestAsyncCachingExecPrivateFields {

@Test
void testQualifiedPrivateFieldStrippedButRemainderCachedBySharedCache() throws Exception {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturobernalg Does this need to be an integration test? Cannot be the same code be tested in TestCachingExecChain or TestAsyncCachingExecChain?

… 9111 section 5.2.2.7)

A shared cache now honours the qualified form of the private response directive. Instead of
rejecting the whole response, it stores the response with the header fields named by the directive
removed from the stored copy, while the response returned to the requesting client retains them; the
directive limits only where a field may be stored, not whether it may be delivered to the client
that issued the request.

The named fields are removed when a response is first stored, when a Vary variant is stored during
negotiation, and when a stored entry is updated by a 304 revalidation. On a revalidation the fields
are removed from both the 304 response and the entry it updates, so the header merge cannot carry a
previously stored field into the updated entry. A bare private directive continues to make the whole
response non-storable by a shared cache, and a repeated private directive is last-value-wins.

The decision is taken in the protocol layer by CachingExec and AsyncCachingExec, reusing the
Cache-Control already parsed for the exchange; the caching backends remain pure storage. The private
field list is parsed the same way as the no-cache field list.
@arturobernalg
arturobernalg force-pushed the cache-qualified-private branch from a501878 to da264e2 Compare August 17, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants